#include <iostream>
#include <string>
#include <cstdlib>
#include <cassert>
#include <stdexcept>
#include <sstream>

#include <fstream>

#include "pugi_xml/pugixml.hpp"

const size_t num_of_symbols(const char* utf8_str)
{
	size_t num_of_symbols = 0u;
	assert(utf8_str);
	while (*utf8_str)
	{
		unsigned char first_byte = *utf8_str;
		if (first_byte < 128)
		{
			//ascii symbol
		}
		else if (first_byte < 224)
		{
			++utf8_str;
			assert(utf8_str);
		}
		else if (first_byte < 240)
		{
			++utf8_str;
			assert(utf8_str);
			++utf8_str;
			assert(utf8_str);
		}
		else if (first_byte < 248)
		{
			++utf8_str;
			assert(utf8_str);
			++utf8_str;
			assert(utf8_str);
			++utf8_str;
			assert(utf8_str);
		}
		else
		{
			std::stringstream ss;
			ss << "can't read utf8 string" << std::endl;
			throw std::runtime_error(ss.str());
		}
		++utf8_str;
		++num_of_symbols;
	}
	return num_of_symbols;
}

void convert_to_unicode(const char* utf8_str, char32_t* unicode_str)
{
	assert(utf8_str);
	size_t buf_index = 0;
	while (*utf8_str)
	{
		unsigned char first_byte = *utf8_str;
		if (first_byte < 128)
		{
			// ascii symbol
			unicode_str[buf_index++] = static_cast<char32_t>(first_byte);
		}
		else if (first_byte < 224)
		{
			++utf8_str;
			assert(utf8_str);
			unsigned char second_byte = *utf8_str;
			char32_t code = ((first_byte & 0x1F) << 6) | (second_byte & 0x3F);
			unicode_str[buf_index++] = code;
		}
		else if (first_byte < 240)
		{
			++utf8_str;
			assert(utf8_str);
			unsigned char second_byte = *utf8_str;
			++utf8_str;
			assert(utf8_str);
			unsigned char thirs_byte = *utf8_str;
			char32_t code = ((first_byte & 0xF) << 12)
					| ((second_byte & 0x3F) << 6) | (thirs_byte & 0x3F);
			unicode_str[buf_index++] = code;
		}
		else if (first_byte < 248)
		{
			++utf8_str;
			assert(utf8_str);
			unsigned char second_byte = *utf8_str;
			++utf8_str;
			assert(utf8_str);
			char thirs_byte = *utf8_str;
			++utf8_str;
			assert(utf8_str);
			unsigned char fourth_byte = *utf8_str;
			char32_t code = (first_byte & 0x7) << 18
					| (second_byte & 0x3F) << 12 | (thirs_byte & 0x3F) << 6
					| (fourth_byte & 0x3F);
			unicode_str[buf_index++] = code;
		}
		else
		{
			std::stringstream ss;
			ss << "can't convert utf8 string (" << utf8_str
					<< ") error after buf_index (" << buf_index
					<< ")" << "\n";
			throw std::runtime_error(ss.str());
		}
		++utf8_str;
	}
	unicode_str[buf_index] = 0;
}

void convert_to_unicode(const char* utf8_str, std::u32string& unicode_str)
{
	assert(utf8_str);
	size_t buf_index = 0;
	while (*utf8_str)
	{
		unsigned char first_byte = *utf8_str;
		if (first_byte < 128)
		{
			// ascii symbol
			unicode_str += static_cast<char32_t>(first_byte);
		}
		else if (first_byte < 224)
		{
			++utf8_str;
			assert(utf8_str);
			unsigned char second_byte = *utf8_str;
			char32_t code = ((first_byte & 0x1F) << 6) | (second_byte & 0x3F);
			unicode_str += code;
		}
		else if (first_byte < 240)
		{
			++utf8_str;
			assert(utf8_str);
			unsigned char second_byte = *utf8_str;
			++utf8_str;
			assert(utf8_str);
			unsigned char thirs_byte = *utf8_str;
			char32_t code = ((first_byte & 0xF) << 12)
					| ((second_byte & 0x3F) << 6) | (thirs_byte & 0x3F);
			unicode_str += code;
		}
		else if (first_byte < 248)
		{
			++utf8_str;
			assert(utf8_str);
			unsigned char second_byte = *utf8_str;
			++utf8_str;
			assert(utf8_str);
			char thirs_byte = *utf8_str;
			++utf8_str;
			assert(utf8_str);
			unsigned char fourth_byte = *utf8_str;
			char32_t code = (first_byte & 0x7) << 18
					| (second_byte & 0x3F) << 12 | (thirs_byte & 0x3F) << 6
					| (fourth_byte & 0x3F);
			unicode_str += code;
		}
		else
		{
			std::stringstream ss;
			ss << "can't convert utf8 string (" << utf8_str
					<< ") error after buf_index (" << buf_index
					<< ")" << "\n";
			throw std::runtime_error(ss.str());
		}
		++utf8_str;
	}
}


//[code_traverse_walker_impl
struct simple_walker: pugi::xml_tree_walker
{
    virtual bool for_each(pugi::xml_node& node)
    {
        if (node.attribute("text"))
        {
           	std::string utf8 = node.attribute("text").as_string();

//           	первый способ
           	const size_t num_symbols = num_of_symbols(utf8.c_str());
           	char32_t wstr[num_symbols];
           	convert_to_unicode(utf8.c_str(), wstr);
        	std::ofstream out_file("new_strings.xml", std::ios::binary | std::ios::app);
        	out_file.write(reinterpret_cast<const char*>(wstr),
        			sizeof(char32_t) * num_symbols);
        	out_file.close();

//           	второй способ
//           	const size_t num_symbols = num_of_symbols(utf8.c_str());
//           	std::u32string wstr;
//           	convert_to_unicode(utf8.c_str(), wstr);
//           	std::ofstream out_file("new_strings.xml", std::ios::binary | std::ios::app);
//           	out_file.write(reinterpret_cast<const char*>(wstr.c_str()),
//           	sizeof(char32_t) * wstr.length());
//           	out_file.close();
        }

        return true; // continue traversal
    }
};
//]

int main()
{
	const char* input_filepath = "strings.xml";

	pugi::xml_document xml_doc;
	pugi::xml_parse_result result = xml_doc.load_file(input_filepath);
	if (!result)
	{
		std::cout << "ERROR: " << result.description() << std::endl;
		return EXIT_FAILURE;
	}

	try
	{
		std::ofstream out_file("new_strings.xml", std::ios::trunc);
		out_file.close();
		simple_walker walker;
		xml_doc.traverse(walker);
	}
	catch (std::exception& ex)
	{
		std::cout << ex.what() << std::endl;
		return EXIT_FAILURE;
	}

	return EXIT_SUCCESS;
}



